package com.gitee.wsl.transform.encode.base

import com.gitee.wsl.transform.encode.DecodeToString
import com.gitee.wsl.transform.encode.StringEncode
import com.gitee.wsl.transform.encode.StringToByteArrayEncodeDecode

object Base58String : StringToByteArrayEncodeDecode {
     object Prefix {
         const val PubkeyAddress: Byte = 0.toByte()
         const val ScriptAddress: Byte = 5.toByte()
         const val SecretKey: Byte = 128.toByte()
         const val PubkeyAddressTestnet: Byte = 111.toByte()
         const val ScriptAddressTestnet: Byte = 196.toByte()
         const val SecretKeyTestnet: Byte = 239.toByte()
    }

    private const val pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

    private val mapBase58 = intArrayOf(
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1,
        -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,
        22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1,
        -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46,
        47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
    )

    override fun decode(src: ByteArray): String {
        // Skip & count leading zeroes.
        var zeroes = 0
        var length = 0
        var begin = 0
        val end = src.size
        while (begin != end && src[begin] == 0.toByte()) {
            begin++
            zeroes++
        }
        // Allocate enough space in big-endian base58 representation.
        val size = (end - begin) * 138 / 100 + 1 // log(256) / log(58), rounded up.
        val b58 = ByteArray(size)
        // Process the bytes.
        while (begin != end) {
            var carry = src[begin].toInt() and 0xff
            // Apply "b58 = b58 * 256 + ch".
            var it = b58.size - 1
            var i = 0
            while ((carry != 0 || i < length) && (it >= 0)) {
                carry += 256 * b58[it]
                b58[it] = (carry % 58).toByte()
                carry /= 58
                i++
                it--
            }
            //assert(carry == 0);
            length = i
            begin++
        }
        // Skip leading zeroes in base58 result.
        var it = size - length
        while (it != b58.size && b58[it] == 0.toByte()) {
            it++
        }
        // Translate the result into a string.
        val str = StringBuilder()
        repeat(zeroes) { str.append('1') }
        while (it < b58.size) {
            str.append(pszBase58[b58[it].toInt()])
            it++
        }
        return str.toString()
    }


    override fun encode(src: String): ByteArray {
        // Skip leading spaces.
        var psz = 0
        while (psz < src.length && src[psz].isWhitespace()) {
            psz++
        }
        // Skip and count leading '1's.
        var zeroes = 0
        var length = 0
        while (psz < src.length && src[psz] == '1') {
            zeroes++
            psz++
        }
        // Allocate enough space in big-endian base256 representation.
        val size = (src.length - psz) * 733 / 1000 + 1 // log(58) / log(256), rounded up.
        val b256 = ByteArray(size)
        // Process the characters.
        while (psz < src.length && !src[psz].isWhitespace()) {
            // Decode base58 character
            var carry = mapBase58[src[psz].code]
            require(carry != -1)
            var i = 0
            var it = b256.size - 1
            while ((carry != 0 || i < length) && (it >= 0)) {
                carry += 58 * (b256[it].toInt() and 0xff)
                b256[it] = (carry % 256).toByte()
                carry /= 256
                it--
                i++
            }
            //assert(carry == 0);
            length = i
            psz++
        }
        // Skip trailing spaces.
        while (psz < src.length && src[psz].isWhitespace()) psz++
        require(psz == src.length){"length not equal $psz  and ${src.length}"}
        // Skip leading zeroes in b256.
        var it = size - length
        val output = ByteArray(zeroes + b256.size - it)
        while (it < b256.size) {
            output[zeroes] = b256[it]
            zeroes++
            it++
        }
        return output
    }
}

val StringEncode.Companion.base58:StringEncode<ByteArray> get() = Base58String

val DecodeToString.Companion.base58:DecodeToString<ByteArray> get() = Base58String

fun String.encodeBase58() = Base58String.encode(this)

fun ByteArray.decodeBase58() = Base58String.decode(this)

fun String.isBase58EncodedString(): Boolean = Base58.alphabet.matches(this)
//
//@OptIn(ExperimentalUnsignedTypes::class)
//class Base58(private val encode: String) {
//    val decode = ByteArray(128) { -1 }
//
//    init {
//        for ((i, v) in encode.withIndex()) {
//            decode[v.code] = i.toByte()
//        }
//    }
//
//    fun encode(data: ByteArray): String {
//        var zcount = 0
//        while (zcount < data.size && data[zcount] == 0.toByte()) {
//            zcount++
//        }
//
//        var size = zcount + (data.size - zcount) * 555 / 406 + 1
//        val encoded = ByteArray(size)
//
//        var high = size - 1
//        for (b in data) {
//            var i = size - 1
//            var carry = b.toUByte().toUInt()
//            while (i > high || carry != 0u) {
//                carry += 256u * encoded[i].toUInt()
//                encoded[i] = (carry % 58u).toByte()
//                carry /= 58u
//                i--
//            }
//            high = i
//        }
//
//        var i = zcount
//        while (i < size && encoded[i] == 0.toByte()) {
//            i++
//        }
//
//        size = encoded.size - i + zcount
//        for (j in 0 until size) {
//            val index = encoded[i - zcount + j].toInt()
//            encoded[j] = encode[index].code.toByte()
//        }
//        return encoded.decodeToString()
//    }
//
//    fun decode(input: String): ByteArray {
//        if (input.isEmpty()) {
//            return ByteArray(0)
//        }
//        val zero = encode[0]
//        val b58sz = input.length
//
//        var zcount = 0
//        var i = 0
//        while (i < b58sz && input[i] == zero) {
//            zcount++
//            i++
//        }
//
//        var t: ULong
//        var c: ULong
//
//        // the 32bit algo stretches the result up to 2 times
//        val binu = ByteArray(2 * ((b58sz * 406 / 555) + 1))
//        val outi = UIntArray((b58sz + 3) / 4)
//        for (r in input) {
//            if (r.code > 127) {
//                throw IllegalArgumentException("high-bit set on invalid digit")
//                //return Err("high-bit set on invalid digit")
//            }
//            if (decode[r.code] == (-1).toByte()) {
//                throw IllegalArgumentException("invalid base58 digit ($r)")
//                //return Err("invalid base58 digit ($r)")
//            }
//            c = decode[r.code].toULong()
//            var j = outi.size - 1
//            while (j >= 0) {
//                t = (outi[j]).toULong() * 58u + c
//                c = t shr 32
//                outi[j] = (t and 0xffffffffu).toUInt()
//                j--
//            }
//        }
//        var mask = ((b58sz % 4).toUInt() * 8u)
//        if (mask == 0u) {
//            mask = 32u
//        }
//        mask -= 8u
//
//        var outLen = 0
//        for (element in outi) {
//            while (mask < 32u) { // loop relies on uint overflow
//                binu[outLen] = (element shr mask.toInt()).toByte()
//                mask -= 8u
//                outLen++
//            }
//            mask = 24u
//        }
//
//        for (msb in zcount until binu.size) {
//            if (binu[msb].toUByte() > 0u) {
//                return binu.copyOfRange(msb - zcount, outLen)
//            }
//        }
//        return binu.copyOfRange(0, outLen)
//    }
//
//    companion object {
//        private val BtcAlphabet = Base58("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
//        private val FlickrAlphabet = Base58("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ")
//
//        fun encodeToStringBtc(data: ByteArray): String {
//            return BtcAlphabet.encode(data)
//        }
//
//        fun decodeStringBtc(data: String): ByteArray {
//            return BtcAlphabet.decode(data)
//        }
//
//        fun encodeToStringFlickr(data: ByteArray): String {
//            return FlickrAlphabet.encode(data)
//        }
//
//        fun decodeStringFlickr(data: String): ByteArray {
//            return FlickrAlphabet.decode(data)
//        }
//    }
//}

//fun ByteArray.encodeBase58() = Base58.encodeToStringBtc(this)
//
//fun String.decodeBase58() = Base58.decodeStringBtc(this)

fun ByteArray.encodeBase58() = Base58.encode(this)

fun String.decodeBase58() = Base58.decode(this)

object Base58 {

    val alphabet = Regex("[1-9a-km-zA-HJ-NP-Z]+")

    private const val ALPHABET_STRING = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    private val ALPHABET = ALPHABET_STRING.toCharArray()
    private const val ENCODED_ZERO = '1'
    private val INDEXES = createIndexes()

    private fun createIndexes(): IntArray {
        val indexes = IntArray(128) { -1 }
        for (i in ALPHABET.indices) {
            indexes[ALPHABET[i].code] = i
        }
        return indexes
    }

    /**
     * Encodes the given byte array to a Base58 string.
     *
     * @param data The byte array to encode.
     * @return The Base58 encoded string.
     * @throws IllegalArgumentException if the input data is null.
     */
    fun encode(data: ByteArray): String {
        requireNotNull(data) { "Input data cannot be null." }
        if (data.isEmpty()) {
            return ""
        }

        // Count leading zeros.
        val leadingZeros = data.takeWhile { it.toInt() == 0 }.count()

        // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
        val input = data.copyOf() // Create a copy to avoid modifying the original data
        val encoded = CharArray(input.size * 2) // upper bound
        var outputStart = encoded.size
        var inputStart = leadingZeros
        while (inputStart < input.size) {
            val remainder = divmod(input, inputStart, 256, 58)
            encoded[--outputStart] = ALPHABET[remainder.toInt()]
            if (input[inputStart].toInt() == 0) {
                inputStart++ // optimization - skip leading zeros
            }
        }

        // Preserve leading encoded zeros.
        while (outputStart < encoded.size && encoded[outputStart] == ENCODED_ZERO) {
            outputStart++
        }

        repeat(leadingZeros) {
            encoded[--outputStart] = ENCODED_ZERO
        }

        // Return encoded string (including encoded leading zeros).
        return encoded.concatToString(outputStart, encoded.size)
    }

    /**
     * Decodes the given Base58 string to a byte array.
     *
     * @param input The Base58 encoded string to decode.
     * @return The decoded byte array.
     * @throws IllegalArgumentException if the input string is null.
     * @throws IllegalArgumentException if the input string contains invalid Base58 characters.
     */
    fun decode(input: String): ByteArray {
        requireNotNull(input) { "Input string cannot be null." }
        if (input.isEmpty()) {
            return byteArrayOf()
        }

        // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
        val input58 = ByteArray(input.length)
        for (i in input.indices) {
            val charCode = input[i].code
            val digit = if (charCode < 128) INDEXES[charCode] else -1
            if (digit < 0) {
                throw IllegalArgumentException("Invalid character in Base58: '${input[i]}'")
            }
            input58[i] = digit.toByte()
        }

        // Count leading zeros.
        val leadingZeros = input58.takeWhile { it.toInt() == 0 }.count()

        // Convert base-58 digits to base-256 digits.
        val decoded = ByteArray(input.length)
        var outputStart = decoded.size
        var inputStart = leadingZeros
        while (inputStart < input58.size) {
            decoded[--outputStart] = divmod(input58, inputStart, 58, 256)
            if (input58[inputStart].toInt() == 0) {
                inputStart++ // optimization - skip leading zeros
            }
        }

        // Ignore extra leading zeroes that were added during the calculation.
        while (outputStart < decoded.size && decoded[outputStart].toInt() == 0) {
            outputStart++
        }

        // Return decoded data (including original number of leading zeros).
        return decoded.copyOfRange(outputStart - leadingZeros, decoded.size)
    }

    /**
     * Divides a number, represented as an array of bytes each containing a single digit
     * in the specified base, by the given divisor. The given number is modified in-place
     * to contain the quotient, and the return value is the remainder.
     *
     * @param number The number to divide.
     * @param firstDigit The index within the array of the first non-zero digit
     *   (this is used for optimization by skipping the leading zeros).
     * @param base The base in which the number's digits are represented (up to 256).
     * @param divisor The number to divide by (up to 256).
     * @return The remainder of the division operation.
     * @throws IllegalArgumentException if the divisor is zero.
     */
    private fun divmod(number: ByteArray, firstDigit: Int, base: Int, divisor: Int): Byte {
        require(divisor != 0) { "Divisor cannot be zero." }
        // this is just long division which accounts for the base of the input digits
        var remainder = 0
        for (i in firstDigit until number.size) {
            val digit = number[i].toInt() and 0xFF
            val temp = remainder * base + digit
            number[i] = (temp / divisor).toByte()
            remainder = temp % divisor
        }
        return remainder.toByte()
    }
}